package com._101aiot.web.exception;

import com._101aiot.common.resultutil.CommonResult;
import com._101aiot.common.resultutil.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ValidationException;

/**
 * 全局异常处理
 *
 * @author xiongguodong
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    /**
     * 生产环境
     */
    private final static String ENV_PROD = "prod";

    /**
     * 当前环境
     */
    @Value("${spring.profiles.active}")
    private String profile;

    /**
     * 未定义异常
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = {Exception.class})
    public CommonResult<?> sysException(Exception e) {
        // 错误日志输出到log，如果不想展示那么多，就不需要显示那么多信息
        log.error(e.getMessage(), e);
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
            return CommonResult.failed(ResultCode.SERVER_ERROR);
        }
        return CommonResult.failed(e.getMessage());
    }

    /**
     *  自定义断言异常处理
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = {BusinessException.class})
    public CommonResult<?> assertException(BusinessException e) {
        log.error(e.getMessage(), e);
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
            return CommonResult.failed(ResultCode.SERVER_ERROR);
        }
        return CommonResult.businessFailed(e.getMessage(), e.getData());
    }

    /**
     * 业务异常捕捉
     *      一般捕捉断言的异常
     *          hutool的断言
     *          spring的断言
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = {IllegalArgumentException.class})
    public CommonResult<?> assertException(IllegalArgumentException e) {
        log.error(e.getMessage(), e);
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
            return CommonResult.failed(ResultCode.SERVER_ERROR);
        }
        return CommonResult.businessFailed(e.getMessage());
    }


    /**
     *  请求的资源（网页等）不存在！
     * @param e 未找到处理器映射器异常
     * @return 异常结果
     */
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    public CommonResult<?> noHandlerFoundException(NoHandlerFoundException e) {
        log.error("{}", e.getMessage());
        return CommonResult.failed(ResultCode.VALIDATE_FAILED);
    }

    /**
     * 处理参数不合法异常
     * @param e ValidationException
     * @return 异常结果
     */
    @ExceptionHandler(value = {ValidationException.class})
    public CommonResult<?> validationException(ValidationException e) {
        log.error("{}", e.getMessage());
        return CommonResult.failed(ResultCode.PARAM_IS_INVALID, e.getMessage());
    }


    /**
     * 参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public CommonResult<?> handleBindException(BindException e) {
        log.error("参数绑定校验异常", e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 参数校验异常，将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public CommonResult<?> handleValidException(MethodArgumentNotValidException e) {
        log.error("参数绑定校验异常", e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private CommonResult<?> wrapperBindingResult(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
        }
        return CommonResult.failed(ResultCode.PARAM_CHECK_EXCEPTION, msg.substring(2));
    }


    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    public CommonResult<?> handleServletException(Exception e) {
        log.error(e.getMessage(), e);
        String code = "";
        try {
            ResultCode servletExceptionEnum = ResultCode.valueOf(e.getClass().getSimpleName());
            code = servletExceptionEnum.getCode();
        } catch (IllegalArgumentException e1) {
            log.error("class [{}] not defined in enum {}", e.getClass().getName(), ResultCode.class.getName());
        }

        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如404.
            return CommonResult.failed(ResultCode.SERVER_ERROR);
        }

        return CommonResult.failed(code, e.getMessage());
    }

}